package org.NooLab.itexx.app.processes;


import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


import org.NooLab.itexx.app.config.ITexxAppPropertiesIntf;
import org.NooLab.itexx.comm.tcp.box.TcpBox;
import org.NooLab.itexx.comm.tcp.box.infra.DataReceiverIntf;
import org.NooLab.itexx.comm.tcp.box.infra.DataSenderIntf;
import org.NooLab.structures.infra.remotely.ActionRequest;
import org.NooLab.utilities.files.DFutils;
import org.NooLab.utilities.logging.LogControl;
import org.NooLab.utilities.logging.PrintLog;
import org.NooLab.utilities.net.GUID;
import org.NooLab.utilities.objects.StringedObjects;
import org.NooLab.utilities.strings.ArrUtilities;
import org.NooLab.utilities.strings.StringsUtil;

import jlibs.core.lang.JavaProcessBuilder;


/*
ProcessBuilder pb = new ProcessBuilder("java", "-Xmx1024m", "-Xms1024m",
    "-DTOOLS_DIR=/home/IM/work/dist", "-Daoi=whole", 
    "-jar", "/home/IM/work/dist/idt_tools.jar");
 
 */

public class BrowserProcess {
		
	
	String name = "";
	private String guid = "";
	private Process process;
	
	int priority = 4;
	int startingOrder = 0 ;
	
	String instanceGuid = "";
	int proposedPort =0;
	int effectiveClientPort = 0;
	private int tikaServerPort;
	
	
	long startingSince = 0L;

	private long observedProcessExitTime;
	private long observedProcessLaunchTime;
	private long observedUpTime;
	private int processState;
	  

	DataSenderIntf sender = null;
	DataReceiverIntf receiver;
	
	// ......................
	
	JavaProcessBuilder jvm;
	
	int initialHeap;
	int maxHeap ;
	
	String jarFilename="" , mainClass="";
	String jarFilePath="", appWorkingDir="";
	String extLibPath="";

	
	ArrayList<String> classPaths = new ArrayList<String> ();
	ArrayList<String> extLibPaths = new ArrayList<String> ();
	ArrayList<String> extraClassPath = new ArrayList<String> ();
	
	ArrayList<String> arguments = new ArrayList<String> (); 
	
	int exitValue;
	// ......................
	
	// UDP
	
	// Map<String,String> params = new HashMap<String, String>() ;
	
	//
	
	Thread prcStarterThrd;
	ProcessAdmin processAdmin;
	private String extJarFile;
	
	boolean created =false;
	
	
	DFutils fileutil = new DFutils();
	PrintLog out;
	StringedObjects so = new StringedObjects ();
	private ITexxAppPropertiesIntf appProperties;
	
	

	// ========================================================================
	public BrowserProcess( ProcessAdmin procAdmin, int index, String[] args) {

		
		processAdmin = procAdmin;
		arguments = ArrUtilities.changeArraystyle( args );
		
		this.out = processAdmin.out;
		
		sender = procAdmin.commBoxes[index].tcpBox.getTcpSender();
		receiver = procAdmin.commBoxes[index].tcpBox.getTcpReceiver();
		
		
		guid = GUID.randomvalue() ;
		initPathVariables();
		
		appProperties = processAdmin.appCommons.getAppProperties();
		
		
	}
	// ========================================================================
	
	private void initPathVariables(){
		
		String str;
		
		str = processAdmin.homepath ;
		str = processAdmin.binPath ;
		
		jarFilename   = "NooLabNativeBrowserApp.jar" ;
		mainClass     = "org.NooLab.browserapp.NooLabNativeBrowserApp" ;
		jarFilePath   = DFutils.createPath( processAdmin.binPath, jarFilename) ;
		appWorkingDir = processAdmin.binPath; // processAdmin.homepath ;
		extLibPath    = DFutils.createPath( processAdmin.binPath, "lib/") ;
	}
	
	class Launcher implements Runnable{

		
		
		public Launcher(){
			prcStarterThrd = new Thread(this,"bprcStarterThrd") ; 	
		}
		
		public void start(){
			prcStarterThrd.start();
		}
		@Override
		public void run() {
			 
			try {
				
				processState = ProcessAdmin._CO_STATUS_STARTING; 
				
				// launchx() ; 
				launchAsRemote();
				
				processState = ProcessAdmin._CO_STATUS_STOPPED ;
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		
		
	} // class 
	
	public void launch() throws Exception{
		(new Launcher()).start();
	}
	
	public void launchAsRemote() throws Exception{
		
		String path ="", argumentStr="";
		
		for (int i=0;i<arguments.size();i++){
			argumentStr = argumentStr + arguments.get(i) +"  ";
		}

		path = DFutils.createPath(processAdmin.binPath, "NooLabNativeBrowserApp.jar") ;

		out.delay(6000);
		// Process extProcess = Runtime.getRuntime().exec("cmd /c start  "+path+"  "+argumentStr);

		Process extProcess = Runtime.getRuntime().exec("javaw -jar  "+path+"  "+argumentStr);
		extJarFile = path; // used by close() to write a stop file
		
		//adding streamgobbler...
		
		StreamGobbler errorGobbler = new StreamGobbler( processAdmin,1,extProcess.getErrorStream(), "ERR  ");

		// any output?
		StreamGobbler outputGobbler = new StreamGobbler( processAdmin,0,extProcess.getInputStream(), "---");

		// kick them off
		errorGobbler.start();
		outputGobbler.start();
		
		try{
		
			int exitVal = extProcess.waitFor();
		}catch(Exception e){
		}
		
		// System.out.println("ExitValue: " + exitVal);

	}
	
	
	public void launchx() throws Exception{
		
		if (created ==false){
			return;
		}
		
		
		// ProcessBuilder pb = new ProcessBuilder();
		 
		observedProcessLaunchTime = System.currentTimeMillis() ;
		
		// to get the created command:
		String command[] = jvm.command();


		process = jvm.launch(System.out, System.err);
		processState = ProcessAdmin._CO_STATUS_ACTIVE ;
		
		out.delay(500);
		
		// to launch it
		
		 //Read out dir output
        InputStream is = process.getInputStream();
        
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);
        String line;
        
        // System.out.printf("Output of running %s is:\n",  Arrays.toString(command));
        
        String processName = getArgumentValueStr( arguments, "-name"); 
        
        while ((line = br.readLine()) != null) {
        	
        	if (line.contains("@iTexx")){
        		// new CoWorkerMsgDigest( itexx,this, CoWorkerIntf._mainServerPort, line);
        		System.out.println(line);
        	}else{
        		System.out.println(line);
        	}
        }
		
		//Wait to get exit value
        try {

        	exitValue = process.waitFor();
            
            out.print(1,"\n\nExit Value of process <"+processName+"> is "+exitValue);
            												
            if (exitValue<0){
            	observedProcessExitTime = System.currentTimeMillis() ;
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        observedUpTime = observedProcessExitTime - observedProcessLaunchTime ;
        if ((observedProcessExitTime - observedProcessLaunchTime) < 2000){
        	processState = ProcessAdmin._CO_STATUS_STOPPED ;
        	
        	
        	
        	if ((processAdmin.appCommons.isApplicationIsStopping()==false) && (exitValue!=8)){
        		processAdmin.appCommons.getApp().close();
        		throw(new Exception("Process <id:"+guid+"> failed to start")); 
        	}else{
        		
        	}
        }
	}
	

	public void stop() {
		// 
		close();
	}

	public void close() {
		// 
		ActionRequest stoprequest = new ActionRequest("close","immediate");
		String stopMessage = so.encode(stoprequest) ;
		
		try {
			
			// this.sender.send(stopMessage ) ;
			
			writeStopFile();
			
			prcStarterThrd.interrupt() ;
			
		} catch (Exception e) {
		}
		receiver.closePort() ;
	}

	private void writeStopFile() {
		// 
		String sfile ;
		
		if ((extJarFile==null) || (extJarFile.length()==0)){
			return;
		}
		
		String path = DFutils.createPath( appProperties.getOwnBinPath(), "app/browser/");
		
		sfile = StringsUtil.getLastPartOfString( extJarFile, "/") ;
		// sfile = StringsUtil.exchangeFilenameExtension( extJarFile, "stop");
		String ext = StringsUtil.getExtensionFromFilename(sfile);
		sfile = extJarFile.replace(ext, "")+".stop";  
		
		sfile = DFutils.createPath( path ,sfile); 
				
		try{

			if (fileutil.fileexists(sfile)){
				fileutil.deleteFile(sfile);
				out.delay(150) ;
			}
			fileutil.writeFileSimple(sfile, "");

		}catch(Exception e){
		}
		
	}
	/**
	 * returns the complete argument as defined in processDef (type = ExtWorker)
	 * 
	 * @param string
	 * @return
	 */
	public String getArgumentByTopic(String topic) {
		String completeArg="" ;
		
		for (int i=0;i<arguments.size();i++){
			String str = arguments.get(i) ;
			
			if (str.startsWith(topic.trim())){
				completeArg = str ;  
				break;
			}
			
		}//i->
		
		return completeArg;
	}
	
	private String getArgumentValueStr( ArrayList<String> args, String marker) {
	
		String avStr="",str;
		
		for (int i=0;i<args.size();i++){
			if (args.get(i).trim().startsWith(marker)){
				str = args.get(i).trim();
				if (str.indexOf(":")>2){
					avStr = str.split(":")[1];
				}
				break;
			}
		}
		return avStr;
	}


	public void sendRelease( int port) {
		
		String releaseMsg="" ;
		TcpBox tcpBox = new TcpBox ();
		
		DataSenderIntf adhocSender=null ;
		// adhocSender = tcpBox.createSender( port, CoWorkerIntf._mainServerPort, "");
		
		try {
			 
			releaseMsg = "<action start=\"1\" mode=\"release\" >\n<process name=\"\" secret=\""+instanceGuid+"\">";
			
			adhocSender.send(releaseMsg) ;
		
			out.delay( 300 ) ;
			
			adhocSender.release() ;
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}


	public void create() throws IOException{
		
		jvm = new JavaProcessBuilder();
		
		jvm.initialHeap(512); // or jvm.initialHeap("512m");
		jvm.maxHeap(1424); // or jvm.maxHeap("1024m");
		 
		
		String str = DFutils.createPath(processAdmin.browserHomepath,"/");
		jvm.workingDir(new File(str)); // appWorkingDir = bin
		
		String jarCP ;
		if (jarFilePath.contains( jarFilename)==false){
			jarCP = DFutils.createPath(jarFilePath, jarFilename) ;
		}else{
			jarCP = jarFilePath;
		}
		 
		
		// jarCP = "org.NooLab.openNLP";
		jvm.classpath( jarCP);
		// jvm.classpath( new File(jarCP)) ; // relative path from configured working dir
		// jvm.classpath( "org.NooLab.openNLP") ; // relative path from configured working dir
		
		// jvm.classpath( classPaths );
		// jvm.libraryPath( new File(jarFilePath)) ; // 
		
		jvm.extDir(new File(extLibPath));
		for (int i=0;i<extLibPaths.size();i++){
			jvm.libraryPath(new File(extLibPaths.get(i)));
		}
		jvm.endorsedDir(new File(extLibPath));
		
		List<File> classpath = jvm.classpath();
		for (int i=0;i<classpath.size();i++){
			// parent.out.print(2, classpath.get(i).getAbsolutePath() ) ;
		}
		
		// to configure mainclass and its arguments
		jvm = jvm.mainClass(mainClass);// "example.MyTest") ;x
		
		// jvm.arg("-xvf");
		// jvm.arg("testDir");
		for (int i=0;i<arguments.size();i++){
			jvm.arg( arguments.get(i) );
		}
		
		created = true;
		
	}

	// ------------------------------------------------------------------------s

	public void setMainClass(String mainclass) {
		 
		mainClass = mainclass;
	}


	public void setJarFilename(String jarfilename) {
		// 
		jarFilename = jarfilename;
	}


	/**
	 * 
	 * @param jarfilepath needs to be relative to /bin
	 */
	public void setJarFilePath(String jarfilepath) {
		// 
		String dir = processAdmin.appProperties.getOwnBinPath();
		jarFilePath = DFutils.createPath( dir, jarfilepath);
		// jarFilePath = DFutilscreatePath( dir, jarfilepath);
		// jarFilePath = jarfilepath;
	}


	public void setAppWorkingDir(String dir) {
		 
		appWorkingDir = dir;
	}


	public void setExtLibrariesPath(String libfolder) {
		// 
		extLibPath = libfolder ;

		extLibPaths.clear();
		extLibPaths.add(extLibPath);
	}


	public void addExtLibrariesPath(String string) {
		
		extLibPaths.add(extLibPath);
	}


	public String getName() {
		return name;
	}


	public void setName(String name) {
		this.name = name;
	}


	public int getInitialHeap() {
		return initialHeap;
	}


	public void setInitialHeap(int initialHeap) {
		this.initialHeap = initialHeap;
	}


	public int getMaxHeap() {
		return maxHeap;
	}


	public void setMaxHeap(int maxHeap) {
		this.maxHeap = maxHeap;
	}


	public String getGuid() {
		return guid;
	}


	public void setGuid(String guid) {
		this.guid = guid;
	}


	public Process getProcess() {
		return process;
	}


	public PrintLog getOut() {
		return out;
	}


	public void addArgument(String arg) {

		arguments.add(arg);
		
	}


	public ArrayList<String> getClassPaths() {
		return classPaths;
	}


	public void addExtraClassPath(String classpath) {

		extraClassPath.add(classpath) ;
		
	}


	public void setInstanceGuid(String guid) {
		// 
		instanceGuid = guid;
	}


	public String getInstanceGuid() {
		return instanceGuid;
	}


	public void setTikaServerPort(int tikaserverport) {
		tikaServerPort = tikaserverport;
	}
	public int getTikaServerPort() {
		return tikaServerPort;
	}


	public void setProposedPort(int port) {
		proposedPort = port;
	}
	public int getProposedPort() {
		return proposedPort ;
	}


	public int getEffectiveClientPort() {
		return effectiveClientPort;
	}


	public void setEffectiveClientPort(int srvPort, int port) {
		
		if (port>1100){
			
			effectiveClientPort = port;
			
			TcpBox tcpBox = new TcpBox();
		 
			sender = tcpBox.createSender( port, srvPort, "") ;
		}
	}



	public int getProcessState() {
		return processState;
	}

	public void setProcessState(int processState) {
		this.processState = processState;
	}

	public long getObservedProcessExitTime() {
		return observedProcessExitTime;
	}

	public long getObservedProcessLaunchTime() {
		return observedProcessLaunchTime;
	}

	public long getObservedUpTime() {
		return observedUpTime;
	}

	public void showArguments() {
		// ArrayList<String> arguments
		
		out.print(2, "\nLaunching CoWorker <"+name+"> with arguments ...");
		
		for (int i=0;i<arguments.size();i++){
			out.print(2, "   "+arguments.get(i));
		}// i->
		
		out.print(2, "\n\n");
		
	}
	
}



class StreamGobbler extends Thread {
	InputStream is;
	String type,prefix ;
	private PrintLog out;
	int errStreamFlag=0;
	
	StreamGobbler(ProcessAdmin processAdmin, int errStreamFlag, InputStream is, String type) {
		this.is = is;
		this.type = type;
		
		out = processAdmin.out ;
		
		this.errStreamFlag = errStreamFlag;
		String str = out.getPrefix();
		prefix = str.replace("]", "-NNB]") ;
		
	}

	public void run() {
		
		try {
			InputStreamReader isr = new InputStreamReader(is);
			BufferedReader br = new BufferedReader(isr);
			String line = null;
			while ((line = br.readLine()) != null) {
				if (line.contains("Duplicate name in Manifest")){
					return;
				}
				
				/*
				if (errStreamFlag >0){
					out.printErr(1, prefix + " "+ type + line);
				}else{
					if (out.getPrintlevel()<=LogControl.Level){
						System.out.println( prefix + " "+ type + line);
					}
				}
				*/
			}

		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}
}